package puntaelena.facilitylocation.hillclimbing.solver.agents;

import java.util.*;

import puntaelena.facilitylocation.hillclimbing.solver.agents.coalitionspool.CoalitionsPair;
import puntaelena.facilitylocation.hillclimbing.solver.agents.coalitionspool.CoalitionPairsPool;
import static puntaelena.facilitylocation.hillclimbing.solver.agents.FcltCityNegotiateComputator.*;
import puntaelena.facilitylocation.hillclimbing.solver.services.*;
import jfuzzyprincepless.agents.BaseNegotiationAgent;
import jfuzzyprincepless.agents.INegotiatingAgent;
import jfuzzyprincepless.negotiation.INegotiationContext;
import jfuzzyprincepless.algorithm.structure.services.IWhileService;
import puntaelena.graphsupport.FacilitiesGraph;
import laboratoryQ.computation.BaseUtils;

public class CoalitionsRegistry
	extends BaseNegotiationAgent
	implements ICoalitionsRegistry, IWhileService
{
	private static final int NOT_INIT_COUNTER_VAL = -1; // TODO: choose other exit-while-strategy

	private int counter = NOT_INIT_COUNTER_VAL;

	private CoalitionPairsPool pool = new CoalitionPairsPool();

	public static CoalitionsRegistry createFromDataWithSeparatedFLServ(
		FacilitiesGraph fgraph,
		boolean[] isFcltOpen,
		int[] witnesses
	) {
		IFcltLocDataService flServ = new DefaultFcltLocDataService();
		flServ.setFacilityLocationData(fgraph, isFcltOpen, witnesses);
		return new CoalitionsRegistry();
	}

	public static ICoalitionsRegistry createFromDataWithSeparatedFLServ(
		FacilitiesGraph fgraph
	) {
		int[] witnesses = new int[fgraph.getCitiesCount()];
		Arrays.fill(witnesses, -1);
		return createFromDataWithSeparatedFLServ(
			fgraph,
			new boolean[fgraph.getFacilitiesCount()],
			witnesses
		);
	}

	@Override
	public boolean negotiate(INegotiationContext context) {
		if (counter == NOT_INIT_COUNTER_VAL) {
			FacilitiesGraph fgraph = context
				.getService(IFcltLocDataService.class).getFgraph();
			counter = BaseUtils.sqare(fgraph.getFacilitiesCount());
			pool.init(fgraph);
		}
		if (counter > 0) {
			counter--;
		} else {
			return false;
		}

		IFcltLocDataService flServ =
			context.getService(IFcltLocDataService.class);
		FacilitiesGraph fgraph = flServ.getFgraph();
		IHillClimbingAgentRegistry hcagReg =
			context.getService(IHillClimbingAgentRegistry.class);
		IFcltCoalitionEventsListener fceListen =
			context.getService(IFcltCoalitionEventsListener.class);

		ICoalitionsListener cltnsListen =
			context.getService(ICoalitionsListener.class);
		cltnsListen.onSearchBestCoalitionPairBeg();
		CoalitionsPair bestPair = pool.searchBestPair(
			flServ,
			hcagReg,
			cltnsListen
		);
		cltnsListen.onSearchBestCoalitionPairEnd();

		// TODO: use this strategy:
//		double profitA = computeOpenCoalitionProfit(msimA);
//		double profitB = computeOpenCoalitionProfit(msimB);
//		double profitAll = computeOpenCoalitionProfit(msimA, msimB);
//		boolean uniteBenefits = profitAll > profitB + profitA;

		boolean uniteBenefits = bestPair.getSimMsr() > -fgraph.getInfEdgeValue();
		if (uniteBenefits) {
			pool.onCoalitionDisappear(bestPair.coalB);
			fceListen.onCoalitionsUnite(bestPair.coalA, bestPair.coalB);
			uniteCoalitions(bestPair.coalA, bestPair.coalB, hcagReg, flServ);
			bestPair.coalA.increaseCoalitionVersion();
		} else {
			fceListen.onNoUnite(bestPair.getSimMsr());
			counter = 0;
		}
		// TODO: possible to split coalition

		return uniteBenefits;
	}

	@Override
	public void increaseCoalitionPairsVersion() {
		pool.increaseCoalitionPairsVersion();
	}

	@Override
	public void onCoalitionVersionIncreased(
		FacilityCoalition coalition
	) {
		pool.invalidateCoalition(coalition);
	}


	private void uniteCoalitions(
		FacilityCoalition coalA,
		FacilityCoalition coalB,
		IHillClimbingAgentRegistry hcagReg,
		IFcltLocDataService flServ
	) {
		assert _ac_intersectionIsEmpty(coalA.getMembers(), coalB.getMembers());
		coalA.getMembers().addAll(coalB.getMembers());
		for (CollaboratingFacilityAgent agent : coalB.getMembers()) {
			agent.setCoalition(coalA);
		}
		assert _ac_noAgentInMultiCoalitions(hcagReg, flServ);
	}

	// IWhileService
	@Override
	public boolean isAgentAlive(
		INegotiatingAgent agent
	) {
		if (counter == NOT_INIT_COUNTER_VAL) {
			return true;
		}
		return counter > 0;
	}

	// assertion checks

	private boolean _ac_noAgentInMultiCoalitions(
		IHillClimbingAgentRegistry hcagReg,
		IFcltLocDataService flServ
	) {
		int facilitiesCount = flServ.getFgraph().getFacilitiesCount();
		int[] ag2coalCount = new int[facilitiesCount];
		for (
			FacilityCoalition coalition :
			getAllCoalitions(hcagReg, flServ)
		) {
			for (CollaboratingFacilityAgent agent : coalition.getMembers()) {
				ag2coalCount[agent.getFacility()]++;
				assert agent.getCoalition(flServ) == coalition;
				if (agent.getCoalition(flServ) != coalition) {
					return false;
				}
			}
		}
		for (int facility = 0; facility < facilitiesCount; facility++) {
			assert ag2coalCount[facility] < 2 : "bad facility: " + facility;
			if (ag2coalCount[facility] > 1) {
				return false;
			}
		}
		return true;
	}

	private static <T> boolean _ac_intersectionIsEmpty(Collection<T> a, Collection<T> b) {
		for (T elem : a) {
			assert !b.contains(elem);
			if (b.contains(elem)) {
				return false;
			}
		}
		return true;
	}
}
